﻿using System;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace PasswordProvider.View.Library
{
    /// <summary>
    /// Class NotificationWindow extends class Window. The window is linked to a "Notification Icon" 
    /// an icon that is displayed in the notification area, the far right area of the task bar. When
    /// the icon is left clicked a context menu may be displayed, and with right clicked this window is shown.
    /// </summary>
    public class NotificationWindow : Window, IDisposable
    {
        #region NotificationWindowPin

        private class NotificationWindowPin
        {
            private int _pinCount;
            public void Set()
            {
                _pinCount++;
            }
            public void Release()
            {
                _pinCount--;
            }
            public bool IsPinned
            {
                get { return _pinCount > 0; }
            }
        }

        #endregion

        #region Fields, Construction

         // This is the icon that shows up in the notification area.
        private NotifyIcon _notifyIcon;

        // Pin status is checked in deactivated event handler, if set, then 
        // the window is not hidden.
        private NotificationWindowPin _pin = new NotificationWindowPin();

        public NotificationWindow()
        {
            Deactivated += new EventHandler(NotificationWindow_Deactivated);

            Loaded += delegate { PositionWindow(); };
        }

 
        #endregion

        #region DependencyProperties
        
        /// <summary>
        /// RunInSystemTrayProperty: Dependency property.
        /// </summary>
        public static readonly DependencyProperty RunInSystemTrayProperty =
           DependencyProperty.Register("RunInSystemTray", typeof(bool), typeof(NotificationWindow),
           new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                  (d, e) =>
                  {
                      (d as NotificationWindow).SetupWindow();
                  }));

        
        /// <summary>
        /// PinWindowOpenProperty: Dependency property.
        /// </summary>
        public static readonly DependencyProperty PinWindowOpenProperty =
           DependencyProperty.Register("PinWindowOpen", typeof(bool), typeof(NotificationWindow),
           new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                  (d, e) =>
                  {
                      NotificationWindow window = (NotificationWindow)d;
                      if ((bool)e.NewValue)
                          window.Pin.Set();
                      else
                          window.Pin.Release();
                  }));

        /// <summary>
        /// AlwaysOnTopProperty: Dependency property.
        /// </summary>
        public static readonly DependencyProperty AlwaysOnTopProperty =
           DependencyProperty.Register("AlwaysOnTop", typeof(bool), typeof(NotificationWindow),
           new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                  (d, e) =>
                  {
                      NotificationWindow w = d as NotificationWindow;
                      if (!w.RunInSystemTray)
                          w.Topmost = (bool)e.NewValue;
                  }));

        /// <summary>
        /// MinimizeToSystemTrayProperty: Dependency property.
        /// </summary>
        public static readonly DependencyProperty MinimizeToSystemTrayProperty =
           DependencyProperty.Register("MinimizeToSystemTray", typeof(bool), typeof(NotificationWindow),
           new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        /// <summary>
        /// MinimizeToSystemTrayProperty: Dependency property.
        /// </summary>
        public static readonly DependencyProperty WindowPositionProperty =
           DependencyProperty.Register("WindowPosition", typeof(Rect), typeof(NotificationWindow),
           new FrameworkPropertyMetadata(new Rect(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));                  

        #endregion

        #region Public Properties

        /// <summary>
        /// Get or set the window position and size.
        /// </summary>
        public Rect WindowPosition
        {
            get { return (Rect)GetValue(WindowPositionProperty); }
            set { SetValue(WindowPositionProperty, value); }
        }

        /// <summary>
        /// Get or set whether the window runs in the system tray. This is a dependency property.
        /// </summary>
        public bool RunInSystemTray
        {
            get { return (bool)GetValue(RunInSystemTrayProperty); }
            set { SetValue(RunInSystemTrayProperty, value); }
        }
        /// <summary>
        /// Gets or sets whether the window is pinned open if it is running in the system tray.
        /// Window is not hidden when deactivated. To hide window, user must click the notification icon.
        /// Ignored if RunInSystemTray is false. This is a dependency property.
        /// </summary>
        public bool PinWindowOpen
        {
            get { return (bool)GetValue(PinWindowOpenProperty); }
            set { SetValue(PinWindowOpenProperty, value); }
        }
        /// <summary>
        /// Get or set whether the window should remain on top of other windows even when not the active window.
        /// This property is ignored when operation mode is RunInSystemTray. This is a dependency property.
        /// </summary>
        public bool AlwaysOnTop
        {
            get { return (bool)GetValue(AlwaysOnTopProperty); }
            set { SetValue(AlwaysOnTopProperty, value); }
        }
        /// <summary>
        /// Get or set whether the window is minimized to the system tray and not the task bar. Ignored if
        /// Operation mode is RunInSystemTray. This is a dependency property.
        /// </summary>
        public bool MinimizeToSystemTray
        {
            get { return (bool)GetValue(MinimizeToSystemTrayProperty); }
            set { SetValue(MinimizeToSystemTrayProperty, value); }
        }
        
        /// <summary>
        /// Obtain a reference to the notification icon, the icon which is displayed in the notification area
        /// when running in system tray. Access this property to set the icon image. 
        /// </summary>
        public NotifyIcon NotifyIcon
        {
            get 
            {
                if (_notifyIcon == null)
                    CreateNotificationIcon();
                return _notifyIcon; 
            }
        }

        /// <summary>
        /// Get or set the context menu which is shown on right mouse click on notification icon.        
        /// </summary>
        public ContextMenu NotificationIconContextMenu
        {
            get 
            {
                if (NotifyIcon.ContextMenu == null)
                {
                    NotifyIcon.ContextMenu = new ContextMenu();

                }
                return NotifyIcon.ContextMenu;                
            }
            set
            {
                NotifyIcon.ContextMenu = value;
            }
        }

        #endregion /// Public properties

        #region Protected Methods

        /// <summary>
        /// Shows the window. If running in normal window mode and window state is minimized, the 
        /// window state is changed to normal.
        /// </summary>
        protected void ShowWindow()
        {
            if (!RunInSystemTray)
            {
                if (_notifyIcon != null)
                    _notifyIcon.Visible = false;
                ShowInTaskbar = true;
                //WindowState = WindowState.Normal;
            }
            this.Show();
            this.Activate();
            this.Focus();
        }

        /// <summary>
        /// Set up the window to either run in the system tray or run as a regluar window,
        /// depending on the value of RunInSystemTray
        /// </summary>
        protected virtual void SetupWindow()
        {
            if (RunInSystemTray)
                SetupAsNotificationWindow();
            else
                SetupAsRegularWindow();
        }

        /// <summary>
        /// Performs all setup steps necessary for the window to be a single border window,
        /// detached completely from the system tray.
        /// </summary>
        protected virtual void SetupAsRegularWindow()
        {            
            Hide();

            PositionWindow();
            WindowStyle = WindowStyle.SingleBorderWindow;
            Topmost = AlwaysOnTop;
            ShowInTaskbar = true;
            NotifyIcon.Visible = false;
            
            Show();
        }

        /// <summary>
        /// Performs all setup steps necessary for the window to be shown as a system tray tool
        /// including setting window style to none, making the notification window visible, etc.
        /// </summary>
        protected virtual void SetupAsNotificationWindow()
        {
            // Store the location of the window.
            StoreWindowRect(true);
            
            Hide();
 
            this.Topmost = true;
            this.WindowStyle = WindowStyle.None;

            ShowInTaskbar = false;
            NotifyIcon.Visible = true;

            PositionWindow();
            
            Show();
                       
        }

        /// <summary>
        /// Hide the window. 
        /// </summary>
        protected void HideWindow()
        {
            if(MinimizeToSystemTray)
            {
                NotifyIcon.Visible = true;
            }

            Hide();
        }

        #endregion // Protected

        #region Overrides

        /// <summary>
        /// Setup a hook for intercepting windows messages.
        /// </summary>
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);

            HwndSource window = PresentationSource.FromVisual(this) as HwndSource;
            if(window != null)
                window.AddHook(new HwndSourceHook(NotificationWindowHwndSourceHook));
        }

        /// <summary>
        /// Store the window position and size when closing, 
        /// to enable openning in same location and size next time.
        /// </summary>
        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            base.OnClosing(e);
            StoreWindowRect(!RunInSystemTray);
        }
     
        #endregion

        #region Private Helpers

        /// <summary>
        /// Intercept Minimize command, handle if MinimizeToSystemTray is true.
        /// </summary>
        private IntPtr NotificationWindowHwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            const int WM_SYSCOMMAND = 0x112;
            const int SC_MINIMIZE = 0xF020;

            if (msg == WM_SYSCOMMAND && wParam == (IntPtr)SC_MINIMIZE)
            {
                if (MinimizeToSystemTray)
                {
                    handled = true;
                    
                    HideWindow();
                    return (IntPtr)1;
                }
            }

            return (IntPtr)0;
        }

        /// <summary>
        /// Get the notification pin which is used to force the window to stay visible when deactivated
        /// and running in the system tray. The window can still be hidden by clicking the notification icon. 
        /// </summary>
        private NotificationWindowPin Pin
        {
            get { return _pin; }
        }
        

        private void PositionWindow()
        {            
            // TODO: 
            if (WindowPosition.Width != 0)
                this.Width = WindowPosition.Width;
            if (WindowPosition.Height != 0)
                this.Height = WindowPosition.Height;
            
            if (RunInSystemTray)
            {
                // If the taskbar is set to autohide, then PrimaryScreen.WorkingArea does not include the
                // taskbar regardless of whether the taskbar is visible or not. This method gets the 
                // position of the taskbar whether or not it is visible.
                Rect taskBarPos = TaskbarHelper.TaskbarPosition;
                
                // Use task bar position info if successfully retrieved.
                if (taskBarPos.Width > 0)
                {
                    // if task bar width is greater than height, it is on the bottom of the screen.
                    if (taskBarPos.Width > taskBarPos.Height)
                    {
                        if (taskBarPos.Top > 0)
                        { // Task bar is on the bottom of the screen
                            this.Left = Screen.PrimaryScreen.WorkingArea.Width - this.Width;
                            this.Top = Screen.PrimaryScreen.Bounds.Height - this.Height - taskBarPos.Height;
                        }
                        else
                        {
                            // task bar is on the top of the screen.
                            this.Left = Screen.PrimaryScreen.WorkingArea.Width - this.Width;
                            this.Top = taskBarPos.Height;
                        }
                    }
                    else
                    {
                        if (taskBarPos.Left > 0)
                        { // task bar is on the right side of the screen
                            this.Left = Screen.PrimaryScreen.Bounds.Width - this.Width - taskBarPos.Width;
                            this.Top = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
                        }
                        else
                        {
                            // task bar is on the left.
                            this.Left = taskBarPos.Width;
                            this.Top = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
                        }
                    }
                }                
                else
                {
                    // Task bar position info was not successfully retrieved.
                    this.Left = Screen.PrimaryScreen.WorkingArea.Width - this.Width;
                    this.Top = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
                }
            }
            else // Not running in system tray, use stored window position.
            {
                if (WindowPosition.Left != 0)
                    this.Left = WindowPosition.Left;
                if (WindowPosition.Top != 0)
                    this.Top = WindowPosition.Top;
            }    
        }

        private void ToggleVisibility()
        {
            if (this.Visibility != Visibility.Visible)
                ShowWindow();
            else
                HideWindow();
        }

        void NotificationWindow_Deactivated(object sender, EventArgs e)
        {
            if (RunInSystemTray)
            {
                if (!Pin.IsPinned)
                    HideWindow();
            }
        }
        
        /// <summary>
        /// Sets value of WindowPosition to have the same height and width as the
        /// current view, optionally modifies the position.
        /// </summary>
        /// <param name="includeLocation"></param>
        private void StoreWindowRect(bool includeLocation)
        {
            // Store width and height in all modes.
            Rect position = new Rect();
            position.Width = RestoreBounds.Width;
            position.Height = RestoreBounds.Height;

            if (includeLocation)
            {
                position.Location = new Point(RestoreBounds.Left, RestoreBounds.Top);
            }
            else
                position.Location = WindowPosition.Location;

            WindowPosition = position;
        }

        // Create notification icon only when needed, app might run without ever running in system tray mode.
        private void CreateNotificationIcon()
        {
            if (_notifyIcon == null)
            {
                _notifyIcon = new NotifyIcon();
                // register method to show the window when the icon is clicked.
                NotifyIcon.MouseDown += new MouseEventHandler(HandleNotifyIconMouseDown);

                // Add event handler to window close, make sure notification icon is removed from system tray.
                this.Closed += delegate
                {
                    _notifyIcon.Visible = false;
                };

            }
        }

        private void HandleNotifyIconMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (this.Visibility != Visibility.Visible || WindowState == WindowState.Minimized)
                    ShowWindow();
                else if(RunInSystemTray)
                    HideWindow();
            }
        }
        

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            // A finalizer, if I had one, would call Dispose(false), which would be responsible for 
            // cleaning up unmanaged resources.
            Dispose(true);
            // Prevent finalization code from executing a second time.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Overriders should clean up unmanaged resources if disposing is either true or false. 
        /// Clean up managed resources only when disposing is true. 
        /// Do call base class implementation.
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if(_notifyIcon != null)
                    _notifyIcon.Dispose();
            }
        }

        #endregion
    }

    /// <summary>
    /// Utility class to get position and size information of the task bar. Gets the size and position of the 
    /// taskbar even if taskbar is hidden or in an unusual location.
    /// </summary>
    public static class TaskbarHelper
    {
        /// <summary>
        /// FxCop wants native methods to be put in a NativeMethods class. I'm willing to go along with that.
        /// </summary>
        private static class NativeMethods
        {
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }
        }

        public static Rect TaskbarPosition
        {
            get
            {
                IntPtr hWnd = NativeMethods.FindWindow("Shell_TrayWnd", null);
                NativeMethods.RECT rect = new NativeMethods.RECT();
                bool success = NativeMethods.GetWindowRect(hWnd, ref rect);
                if (success)
                {
                    Rect outRect = new Rect(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
                    return outRect;
                }

                return new Rect();
            }
        }

    }
}
